package com.xbk.seckill.stock.application.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.xbk.seckill.common.cache.distribute.DistributedCacheService;
import com.xbk.seckill.common.cache.model.SeckillBusinessCache;
import com.xbk.seckill.common.constants.SeckillConstants;
import com.xbk.seckill.common.exception.ErrorCode;
import com.xbk.seckill.common.exception.SeckillException;
import com.xbk.seckill.common.lock.DistributedLock;
import com.xbk.seckill.common.lock.factory.DistributedLockFactory;
import com.xbk.seckill.common.model.enums.SeckillStockBucketStatus;
import com.xbk.seckill.stock.application.cache.SeckillStockBucketCacheService;
import com.xbk.seckill.stock.application.model.dto.SeckillStockBucketDTO;
import com.xbk.seckill.stock.application.service.SeckillStockBucketArrangementService;
import com.xbk.seckill.stock.domain.model.entity.SeckillStockBucket;
import com.xbk.seckill.stock.domain.model.enums.SeckillStockBucketArrangementMode;
import com.xbk.seckill.stock.domain.service.SeckillStockBucketDomainService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.ResourceTransactionManager;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

/**
 * 分桶库存编排服务
 *
 * @author xiexu
 * @date 2025/3/8 22:08
 */
@Slf4j
@Service
public class SeckillStockBucketArrangementServiceImpl implements SeckillStockBucketArrangementService {

    @Autowired
    private SeckillStockBucketDomainService seckillStockBucketDomainService;

    @Autowired
    private SeckillStockBucketCacheService seckillStockBucketCacheService;

    @Autowired
    private DistributedCacheService distributedCacheService;

    @Autowired
    private DistributedLockFactory distributedLockFactory;

    @Autowired
    private ResourceTransactionManager resourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    /**
     * 三步走：
     * 1、关闭库存分桶
     * 2、编排分桶库存
     * 3、打开库存分桶
     *
     * @param goodsId         商品id
     * @param stock           库存总数量
     * @param bucketsQuantity 分桶数量
     * @param assignmentMode  编排模式, 1:总量模式; 2:增量模式
     *                        注意：总量模式指的是变更商品的总库存，以此来达到重新编排分桶库存的目的
     *                        增量模式指的是在原有的库存基础上，增加库存，以此来达到重新编排分桶库存的目的
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void arrangeStockBuckets(Long goodsId, Integer stock, Integer bucketsQuantity, Integer assignmentMode) {
        log.info("arrangeBuckets|准备库存分桶|{},{},{}", goodsId, stock, bucketsQuantity);
        // 参数校验
        if (goodsId == null || stock == null || stock <= 0 || bucketsQuantity == null || bucketsQuantity <= 0) {
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        // 获取分布式锁实例
        DistributedLock lock = distributedLockFactory.getDistributedLock(SeckillConstants.getKey(SeckillConstants.GOODS_STOCK_BUCKETS_SUSPEND_KEY, String.valueOf(goodsId)));
        try {
            // 尝试获取锁
            boolean isLock = lock.tryLock();
            if (!isLock) {
                log.info("arrangeStockBuckets|库存分桶时获取锁失败|{}", goodsId);
                return;
            }
            // 手动开启事务
            TransactionStatus transactionStatus = resourceTransactionManager.getTransaction(transactionDefinition);
            try {
                // 关闭库存分桶
                boolean suspendSuccess = seckillStockBucketDomainService.suspendBuckets(goodsId);
                if (!suspendSuccess) {
                    log.info("arrangeStockBuckets|关闭库存分桶失败|{}", goodsId);
                    throw new SeckillException(ErrorCode.BUCKET_CLOSED_FAILED);
                }
                // 手动提交事务（在编排分桶库存时，一定要先确保库存分桶被成功关闭）
                resourceTransactionManager.commit(transactionStatus);
            } catch (Exception e) {
                log.info("arrangeStockBuckets|关闭分桶失败回滚中|{}", goodsId, e);
                // 手动回滚事务
                resourceTransactionManager.rollback(transactionStatus);
            }
            // 根据商品id从数据库中获取库存分桶列表
            List<SeckillStockBucket> buckets = seckillStockBucketDomainService.getBucketsByGoodsId(goodsId);
            // 如果获取到的商品库存信息为空，则初始化分桶数据
            if (CollectionUtil.isEmpty(buckets)) {
                // 初始化库存分桶
                this.initStockBuckets(goodsId, stock, bucketsQuantity);
                return;
            }
            // 总量模式
            if (SeckillStockBucketArrangementMode.isTotalArrangementMode(assignmentMode)) {
                this.arrangeStockBucketsBasedTotalMode(goodsId, stock, bucketsQuantity, buckets);
            } else if (SeckillStockBucketArrangementMode.isIncrementalArrangementMode(assignmentMode)) {
                // 增量模式
                this.rearrangeStockBucketsBasedIncrementalMode(goodsId, stock, bucketsQuantity, buckets);
            }
        } catch (Exception e) {
            log.error("arrangeStockBuckets|库存分桶错误|", e);
            throw new SeckillException(ErrorCode.BUCKET_CREATE_FAILED);
        } finally {
            // 释放锁
            lock.unlock();
            // 打开分桶
            boolean success = seckillStockBucketDomainService.resumeBuckets(goodsId);
            if (!success) {
                log.error("arrangeStockBuckets|打开库存分桶失败|{}", goodsId);
            }
        }
    }

    /**
     * 按照库存增量模式编排库存
     *
     * @param goodsId         商品id
     * @param stock           增加的商品库存
     * @param bucketsQuantity 分桶数量
     * @param buckets         分桶列表
     */
    private void rearrangeStockBucketsBasedIncrementalMode(Long goodsId, Integer stock, Integer bucketsQuantity, List<SeckillStockBucket> buckets) {
        // 获取当前商品总的可用库存
        int availableStock = buckets.stream().mapToInt(SeckillStockBucket::getAvailableStock).sum();
        // 获取当前商品总的初始库存
        int initStock = buckets.stream().mapToInt(SeckillStockBucket::getInitialStock).sum();
        // 计算增加后的可用库存
        int totalAvailableStock = stock + availableStock;
        // 计算增加后的初始库存
        int totalInitStock = stock + initStock;
        // 可用库存不足
        if (totalAvailableStock <= 0) {
            throw new SeckillException(ErrorCode.STOCK_LT_ZERO);
        }
        // 计算已售商品数量
        int soldStock = initStock - availableStock;
        // 已售商品数量大于现有总库存
        if (soldStock > totalInitStock) {
            throw new SeckillException(ErrorCode.BUCKET_SOLD_BEYOND_TOTAL);
        }
        // 提交分桶
        this.submitBuckets(goodsId, totalInitStock, totalAvailableStock, bucketsQuantity);
    }

    /**
     * 按照库存总量模式编排库存
     *
     * @param goodsId         商品id
     * @param stock           商品总库存
     * @param bucketsQuantity 分桶数量
     * @param buckets         分桶列表
     */
    private void arrangeStockBucketsBasedTotalMode(Long goodsId, Integer stock, Integer bucketsQuantity, List<SeckillStockBucket> buckets) {
        // 获取当前商品所有的可用库存
        int availableStock = buckets.stream().mapToInt(SeckillStockBucket::getAvailableStock).sum();
        // 获取当前商品所有的初始库存
        int totalStock = buckets.stream().mapToInt(SeckillStockBucket::getInitialStock).sum();
        // 计算已售商品数量
        int soldStock = totalStock - availableStock;
        // 已售的商品数量 大于 传入的商品总库存
        if (soldStock > stock) {
            throw new SeckillException(ErrorCode.BUCKET_SOLD_BEYOND_TOTAL);
        }
        // 计算当前商品的可用库存量 = 可用库存 + (商品总库存 - 当前商品所有初始库存)
        availableStock = availableStock + (stock - totalStock);
        // 当前商品的可用库存量小于或等于0，则抛出异常
        if (availableStock <= 0) {
            throw new SeckillException(ErrorCode.STOCK_LT_ZERO);
        }
        // 提交分桶
        this.submitBuckets(goodsId, stock, availableStock, bucketsQuantity);
    }

    /**
     * 初始化分桶信息
     *
     * @param goodsId         商品id
     * @param stock           库存数量
     * @param bucketsQuantity 分桶数量
     */
    private void initStockBuckets(Long goodsId, Integer stock, Integer bucketsQuantity) {
        // 初始化库存分桶信息，此时可用库存 = 初始库存
        this.submitBuckets(goodsId, stock, stock, bucketsQuantity);
    }

    /**
     * 提交分桶库存
     * 在持久化到数据库后，同步更新缓存
     *
     * @param goodsId         商品id
     * @param initStock       初始库存
     * @param availableStock  可用库存
     * @param bucketsQuantity 分桶数量
     */
    private void submitBuckets(Long goodsId, Integer initStock, Integer availableStock, Integer bucketsQuantity) {
        // 构建分桶库存
        List<SeckillStockBucket> buckets = this.buildBuckets(goodsId, initStock, availableStock, bucketsQuantity);
        // 编排库存分桶
        boolean success = seckillStockBucketDomainService.arrangeBuckets(goodsId, buckets);
        if (!success) {
            throw new SeckillException(ErrorCode.BUCKET_CREATE_FAILED);
        }
        /**
         * 保存每一项分桶库存到缓存
         * 每个分桶的缓存键格式：goods:buckets:available:{goodsId}:{serialNo}
         * 示例：goods:buckets:available:1001:0 -> 商品1001的第一个分桶的可用库存
         */
        buckets.forEach(bucket -> {

            // 第一步：生成商品分桶总键（包含商品ID）
            String goodsBucketStocksKey = SeckillConstants.getKey(
                    SeckillConstants.GOODS_BUCKET_AVAILABLE_STOCKS_KEY,
                    String.valueOf(goodsId)
            );
            // 第二步：组合分桶序列号生成具体分桶库存键
            String bucketStockKey = SeckillConstants.getKey(
                    goodsBucketStocksKey,
                    String.valueOf(bucket.getSerialNo())
            );

            // 获取当前分桶的可用库存
            Integer availableStockOfBucket = bucket.getAvailableStock();

            // 写入缓存
            distributedCacheService.put(bucketStockKey, availableStockOfBucket);
        });

        // 保存分桶数量到缓存
        String bucketQuantityKey = SeckillConstants.getKey(
                SeckillConstants.GOODS_BUCKETS_QUANTITY_KEY,
                String.valueOf(goodsId)
        );
        distributedCacheService.put(bucketQuantityKey, buckets.size());
    }

    /**
     * 构建分桶库存
     *
     * @param goodsId         商品id
     * @param initStock       初始库存总数
     * @param availableStock  可用库存总数
     * @param bucketsQuantity 分桶数量
     * @return
     */
    private List<SeckillStockBucket> buildBuckets(Long goodsId, Integer initStock, Integer availableStock, Integer bucketsQuantity) {
        // 参数校验
        if (goodsId == null || initStock == null || initStock <= 0 || availableStock == null || availableStock <= 0 || bucketsQuantity == null || bucketsQuantity <= 0) {
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        // 分桶列表
        List<SeckillStockBucket> buckets = new ArrayList<>();
        // 每个分桶的平均初始库存 = 初始库存总数 / 分桶数量
        int initAverageStocks = initStock / bucketsQuantity;
        // 初始库存的余数（均分后还有剩余库存）
        int initPieceStocks = initStock % bucketsQuantity;

        // 每个分桶的可用库存 = 可用库存总数 / 分桶数量
        int avaiableAverageStocks = availableStock / bucketsQuantity;
        // 可用库存的余数（均分后还有剩余库存）
        int avaiablePieceStocks = availableStock % bucketsQuantity;

        // 遍历并创建前 N -1 个分桶（均匀分配）
        for (int i = 0; i < bucketsQuantity - 1; i++) {
            // 每个桶的初始库存 = 平均初始库存，可用库存 = 平均可用库存
            SeckillStockBucket bucket = new SeckillStockBucket(
                    goodsId,
                    initAverageStocks,
                    avaiableAverageStocks,
                    SeckillStockBucketStatus.ENABLED.getCode(),
                    // 分桶索引
                    i);
            // 添加到分桶列表中
            buckets.add(bucket);
        }
        // 处理最后一个分桶（需加上余数库存）
        // 最后一个桶的初始库存 = 平均初始库存 + 初始余数
        int initRestStock = initAverageStocks + initPieceStocks;
        // 最后一个桶的可用库存 = 平均可用库存 + 可用余数
        int availableRestStock = avaiableAverageStocks + avaiablePieceStocks;

        // 计算差值
        // 重要：检查可用库存是否超过初始库存（防止超卖）
        // 例如：初始总库存=100，可用库存=120，此时必须限制可用库存不能超过初始库存
        int subRestStock = availableRestStock - initRestStock;
        // 如果可用库存大于初始库存，则取初始库存
        int finalAvailableStock = subRestStock > 0 ? initRestStock : availableRestStock;
        SeckillStockBucket bucket = new SeckillStockBucket(
                goodsId,
                initRestStock,
                finalAvailableStock,
                SeckillStockBucketStatus.ENABLED.getCode(),
                // 最后一个分桶索引
                bucketsQuantity - 1);
        // 添加到分桶列表中
        buckets.add(bucket);
        // 当可用库存大于初始库存，需要调整分桶的可用库存
        return subRestStock > 0 ? this.buildBuckets(buckets, subRestStock) : buckets;
    }

    /**
     * 调整分桶的可用库存（解决可用库存超过初始库存的问题）
     *
     * @param buckets      分桶列表
     * @param subRestStock 可用库存的差值
     * @return
     */
    private List<SeckillStockBucket> buildBuckets(List<SeckillStockBucket> buckets, int subRestStock) {
        // 如果分桶列表为空，则直接返回
        if (CollectionUtil.isEmpty(buckets)) {
            return buckets;
        }
        // 遍历前 subRestStock 个分桶，逐个增加可用库存
        IntStream.range(0, subRestStock).forEach((i) -> {
            // 获取当前分桶对象
            SeckillStockBucket bucket = buckets.get(i);
            // 将当前可用库存 + 1
            bucket.setAvailableStock(bucket.getAvailableStock() + 1);
            // 更新分桶列表
            buckets.set(i, bucket);
        });
        return buckets;
    }

    @Override
    public SeckillStockBucketDTO getSeckillStockBucketDTO(Long goodsId, Long version) {
        if (goodsId == null) {
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        SeckillBusinessCache<SeckillStockBucketDTO> seckillStockBucketCache = seckillStockBucketCacheService.getTotalStockBuckets(goodsId, version);
        // 稍后再试，前端需要对这个状态做特殊处理，即不去刷新数据，静默稍后再试
        if (seckillStockBucketCache.isRetryLater()) {
            throw new SeckillException(ErrorCode.RETRY_LATER);
        }
        // 缓存中不存在商品库存数据
        if (!seckillStockBucketCache.isExist()) {
            throw new SeckillException(ErrorCode.STOCK_IS_NULL);
        }
        return seckillStockBucketCache.getData();
    }

}
